package com.lhl.security.jwt.util;

import com.lhl.security.jwt.domain.dto.JwtUser;
import com.lhl.security.jwt.domain.entity.Role;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.swagger.annotations.Api;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;

import java.util.*;

/**
 * @className: com.lhl.security.jwt.util.JwtTokenUtils
 * @description: JWT相关工具类
 * @author: king
 * @date: 2020-11-18
 **/
@Api(tags = "JwtTokenUtils", produces = "")
public class JwtTokenUtils {
    /**
     * 请求头中token的头
     */
    public static final String TOKEN_HEADER = "token";

    /**
     * token的前缀
     */
    public static final String TOKEN_PREFIX = "Bearer ";
    /**
     * 密钥key
     */
    private static final String SECRET = "zyufocus0123456789zyufocus0123456789";

    /**
     * JWT的发行人
     */
    private static final String ISS = "zyu";

    /**
     * 自定义信息中用户角色
     */
    private static final String ROLE_CLAIMS = "role";
    /**
     * 自定义信息中用户ID
     */
    private static final String UID_CLAIMS = "uid";
    /**
     * 自定义信息中用户名
     */
    private static final String UNAME_CLAIMS = "uname";

    /**
     * 过期时间是3600秒，既是60min
     */
    public static final long EXPIRATION = 3600L * 1000;

    /**
     * 选择了记住我之后的过期时间为1小时
     */
    public static final long EXPIRATION_REMEMBER = 3600L * 1000;

    /**
     * 创建token
     * @param details 用户登录信息
     * @param isRememberMe 是否记住我
     * @return
     */
    public static String createToken(JwtUser details, boolean isRememberMe) {
        // 如果选择记住我，则token的过期时间为
        long expiration = isRememberMe ? EXPIRATION_REMEMBER : EXPIRATION;
        HashMap<String, Object> map = new HashMap<>();
        map.put(ROLE_CLAIMS, details.getAuthorities()); // 角色名字
        map.put(UID_CLAIMS, details.getId()); // 用户ID
        map.put(UNAME_CLAIMS, details.getUsername()); // 用户名

        return Jwts.builder().signWith(SignatureAlgorithm.HS256, SECRET) // 加密算法
                .setClaims(map) // 自定义信息
                .setIssuer(ISS) // jwt发行人
                .setSubject(details.getUsername()) // jwt面向的用户
                .setIssuedAt(new Date()) // jwt发行时间
                .setExpiration(new Date(System.currentTimeMillis() + expiration)) // key过期时间
                .compact();
    }

    /**
     * 获取用户信息
     *
     * @param token
     * @return
     */
    @SuppressWarnings("unchecked")
    public static JwtUser getUserDetail(String token) {
        Claims claims = getTokenBody(token);
        JwtUser user = null;
        if (claims != null && claims.size() > 0) {
            user = new JwtUser();
            if (claims.get(ROLE_CLAIMS) != null) {
                ArrayList<Role> roles = new ArrayList<>();
                ArrayList<LinkedHashMap<String, String>> list = (ArrayList<LinkedHashMap<String, String>>) claims
                        .get(ROLE_CLAIMS);
                for (LinkedHashMap<String, String> l : list) {
                    roles.add(Role.valueOf(l.get("authority").replace("ROLE_", "")));
                }
                user.setRoles(roles);
            }
            if (claims.get(UID_CLAIMS) != null) {
                user.setId((Integer) (claims.get(UID_CLAIMS)));
            }
            if (claims.get(UNAME_CLAIMS) != null) {
                user.setUsername((String) (claims.get(UNAME_CLAIMS)));
            }
        }

        return user;
    }

    /**
     * 获取Token有效期ms
     *
     * @param token
     * @return
     */
    public static long getExpireTime(String token) {
        Claims claims = getTokenBody(token);
        return claims.getExpiration().getTime() - claims.getIssuedAt().getTime();
    }

    /**
     * 从token获取用户信息
     *
     * @param token
     * @return
     */
    public static String getUsername(String token) {
        return getTokenBody(token).getSubject();
    }

    /**
     * 从token中获取用户角色
     *
     * @param token
     * @return
     */
    public static Set<String> getUserRole(String token) {
        List<GrantedAuthority> userAuthorities = (List<GrantedAuthority>) getTokenBody(token).get(ROLE_CLAIMS);
        return AuthorityUtils.authorityListToSet(userAuthorities);
    }

    /**
     * 从token中获取用户ID
     *
     * @param token
     * @return
     */
    public static String getUserID(String token) {
        return (String) getTokenBody(token).get(UID_CLAIMS);
    }

    /**
     * 是否已过期
     *
     * @param token
     * @return
     */
    public static boolean isExpiration(String token) {
        return getTokenBody(token).getExpiration().before(new Date());
    }

    /**
     * 刷新token
     *
     * @param token  原token
     * @param always 强制刷新，true有效
     * @return
     */
    public static String refreshToken(String token, boolean always) {
        boolean canRefresh = canRefresh(token);
        String news = null;
        if (canRefresh == true || always == true) {
            Claims claims = getTokenBody(token);
            HashMap<String, Object> map = new HashMap<>();
            map.put(ROLE_CLAIMS, claims.get(ROLE_CLAIMS));
            map.put(UID_CLAIMS, claims.get(UID_CLAIMS));
            map.put(UNAME_CLAIMS, claims.get(UNAME_CLAIMS));
            news = Jwts.builder().signWith(SignatureAlgorithm.HS256, SECRET) // 加密算法
                    .setClaims(map) // 自定义信息
                    .setIssuer(ISS) // jwt发行人
                    .setSubject((String) claims.get(UNAME_CLAIMS)) // jwt面向的用户
                    .setIssuedAt(new Date()) // jwt发行时间
                    .setExpiration(new Date(System.currentTimeMillis()
                            + (claims.getExpiration().getTime() - claims.getIssuedAt().getTime()))) // key过期时间
                    .compact();
        } else {
            news = token;
        }
        return news;
    }

    /**
     * 是否可刷新
     *
     * @param token
     * @return
     */
    public static boolean canRefresh(String token) {
        Claims claims = getTokenBody(token);
        long create = claims.getIssuedAt().getTime();
        long expire = claims.getExpiration().getTime();

        // 有效期不足一半，可刷新
        return new Date().getTime() - create > (expire - create) / 2;
    }

    private static Claims getTokenBody(String token) {
        return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
    }

    /**
     * 验证token
     *
     * @param token
     * @return
     */
    public static boolean validateToken(String token) {
        return (isExpiration(token) == false);
    }

}
